package chapter6;

import chapter4.Dish;

import java.util.*;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.*;

public class Note {
    /**
     * 收集器简介
     *
     * 预定义收集器
     *  Collectors类提供的工厂方法创建的收集器 主要提供3大功能：
     *      1、将流元素规约和汇总为一个值
     *      2、元素分组
     *      3、元素分区
     *  归约和汇总
     *      但凡要把流中所有的项目合并成一个结果时就可以使用 这个结果可以是任何类型
     */
    List<Dish> menu = new ArrayList<>();
    long howManyDishes = menu.stream().collect(Collectors.counting());

    /**
     * 查找流中的最大值和最小值
     *  需要使用两个收集器：Collectors.maxBy
     *  Optional<Dish> 这个代表什么？
     *      Java 8引入了Optional 它是一个容器 可以包含也可以不包含值
     */
    Comparator<Dish> dishCaloriesComparator = Comparator.comparing(Dish::getCalories);

    Optional<Dish> mostCalorieDish = menu.stream().collect(maxBy(dishCaloriesComparator));

    /**
     * 汇总
     *  求和：Collectors.summingInt
     *  求平均：Collectors.averagingInt
     *  一次求多个值：summarizingInt
     *  连接字符串：joining
     */
    int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));

    double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories));
    //IntSummaryStatistics 这个类包含了count、sum、min、average以及max属性
    IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));

    String shortMenu = menu.stream().map(Dish::getName).collect(joining());

    //还能指定连接符
    String shortMenu2 = menu.stream().map(Dish::getName).collect(joining(", "));

    /**
     * 广义的规约汇总
     *  前面所有的收集器 都是可以用reducing工厂方法定义的规约过程的特殊情况而已  Collectors.reducing工厂方法是所有这些特殊情况的一般化
     */
    int totalCalories2 = menu.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));

    /**
     * 分组
     *  给groupBy方法传递一个Function(以方法引用的形式即Dish::getType)，它提取流中的每一道Dish的Dish.Type 我们把这个
     *  Function叫做分类函数 因为它用来把流中的元素分成不同的组
     *  分组的结果是Map 把分组函数返回的值作为键 把流中所有具有这个分类值的项目的列表作为对应的值
     *
     *  分类函数不一定必须是方法引用  如果分类函数比较复杂  可以使用Lambda的形式
     */
    Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));

    Map<Dish.CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(
            groupingBy(dish  -> {
                if (dish.getCalories() <= 400) return Dish.CaloricLevel.DIET;
                else if ( dish.getCalories() <=700) return Dish.CaloricLevel.NORMAL;
                else return Dish.CaloricLevel.FAT;
            })
    );

    /**
     * 多级分组
     *  要实现多级分组  我们可以使用一个由双参数版本的Collectors.groupingBy工厂方法创建的收集器 它除了普通的分类函数之外，还可以接受Collector类型的第二个参数
     *  要进行二级分组的话 我们可以把一个内层groupingBy传递给外层groupingBy 并定义一个为流中项目分类的二级标准
     */
    Map<Dish.Type, Map<Dish.CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = menu.stream().collect(
            //一级分类函数
            groupingBy(Dish::getType,
                    //二级分类函数
                    groupingBy( dish -> {
                if (dish.getCalories() <= 400) return Dish.CaloricLevel.DIET;
                else if (dish.getCalories() <= 700) return Dish.CaloricLevel.NORMAL;
                else return Dish.CaloricLevel.FAT;
            }))
    );

    /**
     * 按子组收集数据
     *  上个demo中 我们把第二个groupingBy收集器传递给了外层收集器来实现多级分组，但传递给第一个groupingBy的第二个收集器可以是任何类型 而不一定是另一个groupingBy
     *
     *  例如 我们现在想统计每类菜单中有多少个菜
     *      我们可以将counting收集器 传递给 第一个groupingBy收集器
     *  注意 普通的单参数groupingBy(f)[其中f是分类函数] 实际上是groupingBy(f, toList())的简便写法
     */
    Map<Dish.Type, Long> typesCount = menu.stream().collect(groupingBy(Dish::getType,counting()));

    /**
     * 把收集器的结果转换为另一种类型
     *  使用Collectors.collectingAndThen工厂方法返回的收集器
     *    工厂方法接收两个参数：
     *          要转换的收集器
     *          转换函数
     *       返回另一个收集器 这个收集器相当于旧收集器的一个包装
     *    collect操作的最后一步就是将返回值用转换函数做一个映射
     */

    //查找每个子组中热量最高的Dish
    Map<Dish.Type, Dish> msotCaloricByType =
            menu.stream()
            .collect(groupingBy(Dish::getType,
                    collectingAndThen(
                            //包装后的收集器
                            maxBy(Comparator.comparingInt(Dish::getCalories)),
                            //转换函数
                            Optional::get
                    )));
    /**
     * 与groupingBy联合使用的其他收集器的例子
     *  一般 通过groupingBy工厂方法的第二个参数传递的收集器将会对同一组的所有流元素执行进一步规约操作
     */

    //对每组Dish 求热量和
    Map<Dish.Type, Integer> totalCaloriesByType = menu.stream().collect(groupingBy(Dish::getType,summingInt(Dish::getCalories)));

    /**
     * 然而 常常和groupingBy联合使用的另一个收集器是mapping方法生成的  这个方法接受2个参数：一个函数对流中的元素做变换，另一个则将变换的结果收集起来
     */

    //统计 每中类型Dish菜单中 都有哪些CaloricLevel
    Map<Dish.Type, Set<Dish.CaloricLevel>> caloricLevelsByType =
            menu.stream().collect(
                    groupingBy(Dish::getType, mapping(
                            dish -> {if (dish.getCalories() <= 400) return Dish.CaloricLevel.DIET;
                            else if (dish.getCalories() <= 700) return Dish.CaloricLevel.NORMAL;
                            else return Dish.CaloricLevel.FAT;
                            },toSet()
                    ))
            );


    /**
     * 分区
     *      分区是分组的特殊情况：由一个谓词(返回一个布尔值的函数)作为分类函数 称作分区函数  分区函数返回一个布尔值 这意味着得到的分组Map的键类型是Boolean
     *          因此它最多可以分为2组：true是一组  false是一组
     */

    //将菜单中菜  分为素食和非素食的
    Map<Boolean, List<Dish>> partitionedMenu =
            menu.stream().collect(partitioningBy(Dish::isVegetarian)); //分区函数

    /**
     * 收集器接口
     *  Collector接口包含了一系列方法 为实现具体的规约操作(即收集器)提供了范本
     *
     */





}
